package xp.ass.imitate.express.impl;

import lombok.Data;
import xp.ass.imitate.express.meta.XToken;
import xp.ass.imitate.express.meta.XTokenKind;

import java.util.ArrayList;
import java.util.List;

/**
 * @author chent3
 * @date 2024/8/23
 * <p>
 * 分词器
 */
@Data
public class XTokenizer {

    private static final char[] STANDARD_IDENTIFIER = new char[]{
            '#', '(', ')', '{', '}', '.', 0, '\t', '\n', '\r'
    };


    private String input;

    private char[] charsToProcess;

    private int pos;

    private int max;


    //  注意使用这个的时候 一定要初始化，避免外围使用造成空指针
    private List<XToken> xTokenList = new ArrayList<>();

    public XTokenizer(String input) {
        this.input = input;
        // use null character as a sentinel value benefit :
        // we can avoid judging null value
        // notice add a knowledge point： "\0" ==> ASCII(0) ，“0” ==> ASCII(48)
        this.charsToProcess = (input + "\0").toCharArray();
        this.pos = 0;
        this.max = charsToProcess.length;
    }

    public List<XToken> process() {
        while (this.pos < this.max) {
            char ch = this.charsToProcess[this.pos];
            if (isIdentifier(ch)) {
                processIdentifier(ch);
            } else {
                switch (ch) {
                    case '#':
                        pushCharToken(XTokenKind.HASH);
                        break;
                    case '.':
                        pushCharToken(XTokenKind.DOT);
                        break;
                    case '{':
                        pushCharToken(XTokenKind.LCURLY);
                        break;
                    case '}':
                        pushCharToken(XTokenKind.RCURLY);
                        break;
                    case ' ':
                    case '\t':
                    case '\n':
                    case '\r':
                    case '\0': // sentinel value
                        this.pos++;
                        break;
                    default:
                        throw new IllegalArgumentException("unknown token " + ch);
                }
            }
        }
        return this.xTokenList;
    }

    private void processIdentifier(char ch) {
        int start = this.pos;
        do {
            this.pos++;
        } while (this.pos < this.max && isIdentifier(this.charsToProcess[this.pos]));
        this.xTokenList.add(new XToken(XTokenKind.IDENTIFER, start, this.pos));
    }

    private boolean isIdentifier(char ch) {
        for (char identifier : STANDARD_IDENTIFIER) {
            if (ch == identifier) {
                return false;
            }
        }
        return true;
    }

    private void pushCharToken(XTokenKind xTokenKind) {
        this.xTokenList.add(new XToken(xTokenKind, this.pos, 1));
        this.pos++;
    }

}
